/*
 *	CHANGE LOG
 * 
 * 01.02.01
 *
 *		10/22/2008 - DW - Sorted the entries by Title so they are easier to find in the Open dialog.
 *		
 * 01.02.02
 * 
 *      11/30/2009 - DW - Closed the data readers when connections are made to the db. 
 * 
 */


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using DotNetNuke.Common;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Entities.Users;
using DotNetNuke.Security;
using ICG.Modules.ExpandableTextHtml.Components;
using MetaPostProvider.TextHtml;
using DataProvider=DotNetNuke.Data.DataProvider;

namespace ITCrossing
{
	public class metaPostTextHtmlProvider : IPublishable
	{
		# region IPublishable Members

		# region Public Properties
		
		/// <summary>
		/// ProviderKey should be set to the Friendly Name of the module for
		/// which this provider is being implemented.
		/// </summary>
		public string ProviderKey
		{
			get
			{
				return MetaPostServices.GetFriendlyNameFromModuleDefinition("ExpandableTextHtml");
			}
		}
		
		/// <summary>
		/// ManifestFilePath is the path including the filename to the manifest file used to determine 
		/// the settings available in Windows Live Writer.  An example path might
		/// be /DesktopModules/itcMetaPost/manifests/wlwblog.xml.  See the following URL for 
		/// information regarding what Content can be placed in a WLW manifest file:
		/// http://msdn2.microsoft.com/en-us/library/bb463260.aspx
		/// </summary>
		public string ManifestFilePath
		{
			get { return "/DesktopModules/itcMetaPost/manifests/wlwexpandabletexthtml.xml"; }
		}
		
		/// <summary>
		/// LocalizationFilePath is the path including the filename to the 
		/// resource file used by metaPost to retrieve the error messages
		/// shown to the user (eg. "/DesktopModules/itcMetaPost/App_LocalResources/itcMetaPost")
		/// </summary>
		public string LocalizationFilePath
		{
			get { return "/DesktopModules/itcMetaPost/App_LocalResources/itcMetaPostExpandableTextHtml"; }
		}
		
		/// <summary>
		/// OPTIONAL - ImageUploadPath is a property metaPost uses to determine where 
		/// to store the media files (images).  This property should return just a 
		/// section of the path starting from the portal root.  Added to this path will
		/// be the username along with the path 
		/// </summary>
		public string ImageUploadPath
		{
			get
			{
				// This can be overridden with custom implementation if needed.  If
				// overridden, then a message should be placed in the ImagePath column
				// for your provider letting administrators know that administration of
				// the ImagePath is implemented elsewhere.  
				return MetaPostServices.GetProviderImageUploadPath(ProviderKey);
			}
		}

		/// <summary>
		/// AttachmentUploadPath is used to specify the location of attachments added
		/// to a post or page.
		/// </summary>
		public string AttachmentUploadPath
		{
			get
			{
				// For now, we'll just set this to the same as the image upload path
				return MetaPostServices.GetProviderAttachmentUploadPath(ProviderKey);
			}
		}

		/// <summary>
		/// SettingsUserControlPath is for future use.  It will return the path to the
		/// the settings user control for this provider.  
		/// </summary>
		public string SettingsUserControlPath
		{
			get { throw new NotImplementedException(); }
		}

		# endregion

		# region Get Methods

		/// <summary>
		/// The purpose of this procedure is to provide a list of all the modules in a 
		/// given portal to which the currently logged on user has either insert, update
		/// or delete access.  Note, this may not always correspond to an actual module.
		/// For example, with the DNN Blog module provider, a list of blogs is returned
		/// along with URL of where each blog is available within the site.  For the News
		/// Articles module, we return a list of all News Articles modules that have been
		/// added to Tabs throughout the site.
		/// </summary>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>
		/// <param name="providerKey"></param>
		public ModuleInfoStruct[] GetModulesForUser(UserInfo userInfo, PortalSettings portalSettings, string providerKey)
		{
			// For the Expandable Text/HTML module, we simply return a reference to the current portal.
			// Users are able to retreive and edit content from any Expandable Text/HTML module to which they 
			// have edit access through the GetRecentItems method below (available through the Open menu 
			// in Windows Live Writer.
			ModuleInfoStruct[] infoArray = new ModuleInfoStruct[1];

			ModuleInfoStruct moduleInfo = new ModuleInfoStruct();
			moduleInfo.ModuleLevelId = portalSettings.PortalId.ToString();
			moduleInfo.ModuleName = portalSettings.PortalName + " - " + providerKey;
			moduleInfo.Url = MetaPostServices.GetRedirectUrl(providerKey, portalSettings.HomeTabId); 

			infoArray[0] = moduleInfo;

			return infoArray;
		}

		/// <summary>
		/// GetRecentItems returns a list of the recent items added to the module.  For example, with
		/// the blog module, the list would return all the recent blog entries.  For the Text/HTML provider, 
		/// this method would return a list of the recently added Text/HTML modules in the site.
		/// </summary>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="numberOfItems"></param>
		/// <param name="requestType">GetRecentItems can be called for a number of reasons.  In Windows Live Writer,
		/// users are able to retrieve a list of recent posts or recent pages.  The requestType parameter is used
		/// to denote whether a list of pages or posts is being requested.  Additionally, WLW may request a list 
		/// of hiearchical pages to show pages when allowing a parent page to be assigned to a new page.  The values for this enum
		/// are RecentPosts, RecentPages and RecentPagesHiearchical.</param>
		/// <param name="providerKey"></param>
		/// <returns></returns>
		public Item[] GetRecentItems(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, int numberOfItems, RecentItemsRequestType requestType, string providerKey)
		{
			List<Item> itemArray = new List<Item>();

			// List used to retrieve an array of ModuleInfoStructs to return to metaPost.
			List<ModuleInfoStruct> moduleInfoList = new List<ModuleInfoStruct>();

			// dnnModuleInfos is the list which stores the DnnModuleInfoStructs returned from 
			// GetModulesByProviderKeyUserIdAndPortalId.
			List<DnnModuleInfoStruct> dnnModuleInfos
				= MetaPostServices.GetEditableModules(providerKey, userInfo.UserID, portalSettings.PortalId);

			if (dnnModuleInfos.Count > 0)
			{
				int itemCount = 0;
				foreach (DnnModuleInfoStruct dnnModuleInfo in dnnModuleInfos)
				{
					// When requesting Posts, show posts
					if (requestType != RecentItemsRequestType.RecentPages)
					{
						Item item = new Item();
						item.ItemId = dnnModuleInfo.ModuleId.ToString();
						string modulePath = dnnModuleInfo.TabPath.Replace("//", "/");
						// remove the beginning forward slash
						if (modulePath.Length > 1 && modulePath.Substring(0, 1) == "/")
							modulePath = modulePath.Substring(1);
						if (requestType == RecentItemsRequestType.RecentPagesHierarchical)
						{
							item.Title = modulePath + "/" + dnnModuleInfo.ModuleTitle;
						}
						else
						{
							item.Title = dnnModuleInfo.ModuleTitle; 
						}
						item.Content = modulePath;
						item.DateCreated = dnnModuleInfo.CreatedDate;
						// Configure the URL that WLW uses to send the user to after a post has been made
						// We will make use of MetaPostRedirect here to 
						item.Link = Globals.NavigateURL(dnnModuleInfo.TabId);
						itemArray.Add(item);
						itemCount++;
					}
					// When requesting Pages, show pages
					if (requestType == RecentItemsRequestType.RecentPages)
					{
						// Retrieve a list of the pages
						ExpandableTextHtmlController controller = new ExpandableTextHtmlController();
						List<ExpandableTextHtmlInfo> regions = controller.GetExpandableTextHtmls(dnnModuleInfo.ModuleId, string.Empty);
						foreach (ExpandableTextHtmlInfo region in regions)
						{
							Item item = new Item();
							item.ItemId = region.ItemId;
							item.Title = dnnModuleInfo.ModuleTitle + " - " + region.Title;
							item.Content = region.Body;
							item.DateCreated = region.LastUpdated;
							// Configure the URL that WLW uses to send the user to after a post has been made
							// We will make use of MetaPostRedirect here to 
							item.Link = Globals.NavigateURL(dnnModuleInfo.TabId);
							itemArray.Add(item);
							itemCount++;
						}
					}
					if (itemCount >= numberOfItems && numberOfItems != -1) break;
				}
			}
			else
			{
				throw new MetaPostException("NoModulesForUser",
											"There are no News Article modules associated with this user account.  Please enter a user account which has been associated with a blog.");
			}
			
			itemArray.Sort(new ItemComparer());
			return itemArray.ToArray();
		}

		/// <summary>
		/// GetItem is used to retrieve a specific item, which may be a page or a post if your module supports
		/// both concepts.  In cases where a module supports both pages and posts, a concept made available in 
		/// Windows Live Writer, but not in Word 2007 or in many other publishing clients, the itemType parameter 
		/// can be used to distinguish between when this method is being called to add a page or a post.  If your 
		/// provider doesn't support a distinction between pages and posts, then simply ignore the itemType parameter.
		/// </summary>
		/// <param name="itemId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="itemType"></param>
		/// <returns></returns>
		public Item GetItem(string itemId, UserInfo userInfo, PortalSettings portalSettings, ItemType itemType)
		{
			Item item = new Item();

			// This may be called to retrieve an instance of the Expandable Text/HTML module (post)
			// or to retrieve a content region associated with a module (page).

			int contentId = Convert.ToInt32(itemId);
			ProviderStructs.HtmlInfoStruct htmlInfo;

			// We'll retreive the information for the post either way since we need the title
			// for the module for the parent Id

			if (itemType == ItemType.Post)
			{
				htmlInfo = GetHtmlText(contentId);
			}
			else // Otherwise, it's a page (expandable content region)
			{
				int moduleId = GetModuleIdFromItemId(itemId);
				htmlInfo = GetHtmlText(moduleId);
				ExpandableTextHtmlController controller = new ExpandableTextHtmlController();
				ExpandableTextHtmlInfo expInfo = controller.GetExpandableTextHtml(moduleId, contentId);

				// This may seem strange, and it is.  Instead of setting the parentId to an ID, we're 
				// setting it to the Title of the module to compensate for a bug in WLW.  WLW will show 
				// the actual ID instead of the text associated with the ID, so we'll send the Title
				// back as the ID and then in the EditItem method, we'll check to see if the value
				// is numeric.  If it isn't, then we know the user didn't change the parentId value.
				item.ParentId = htmlInfo.Title; // moduleId.ToString();
				
				// Now, we set the rest of the properties of the Page
				htmlInfo.Title = expInfo.Title;
				htmlInfo.DesktopHtml = expInfo.Body;
				htmlInfo.CreatedDate = DateTime.Now; // for now, we'll just use today's date since now created date is saved.
				htmlInfo.Id = expInfo.ItemId; // We'll save the ItemId as the unique identifier
				item.PageOrder = expInfo.SortOrder.ToString();
			}
			
			item.Title = htmlInfo.Title ?? "";
			item.Content = HttpUtility.HtmlDecode(htmlInfo.DesktopHtml);
			item.DateCreated = htmlInfo.CreatedDate;
			item.ItemId = htmlInfo.Id;

			return item;
		}

		# endregion

		# region Add, Edit and Delete Methods

		/// <summary>
		/// NewItem is used for creating new items (either pages or posts).  The implementation 
		/// in your provider can distinguish between page and post requests for NewItem by the 
		/// ItemType property of the Item struct.  Additional properties of the Item Struct which 
		/// may be of interest in the implmentation of this method are: Publish, StyleDetectionPost,StyleId, and ItemType.
		/// See the comments associated with each of these properties of the Item struct for more information.
		/// </summary>
		/// <param name="moduleLevelId">String - ModuleId is a very loose concept here.  Anything can be tracked in this variable as long as it is consistent.  For the Blog module, the BlogId is passed and for the Text/HTML module, the ModuleId is passed and for the Ventrian News Articles module, the ArticleId is passed.  This value is based on the value sent to Windows Live Writer when GetModulesForUser is called. </param>
		/// <param name="userInfo">DotNetNuke UserInfo object.  This UserInfo object is retrieved by metaPost and made available to every method call in the MetaPostProvider interface.</param>
		/// <param name="portalSettings">DotNetNuke PortaSettings object - This is provided to make it easier to work with metaPost.  metaPost does all the work behind the scenes of identifying the right portalSettings object based on the requested URL.</param>
		/// <param name="item">Custom Struct - The item struct contains a list of fields sent through the MetaWeblog API.  The properties have been renamed to make sense in more general applications outside of just blogs.</param>
		/// <returns></returns>
		public string NewItem(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, Item item)
		{
			
			List<string> images = MetaPostServices.GetImageListFromItem(ref item);

			string newId = string.Empty;

			if (item.ItemType == ItemType.Post)
			{
				if (item.StyleDetectionPost)
				{
					try
					{
						// Retrieve the HtmlInfoStruct which is basically our own Info object that we
						// implemented as a struct to make it nice and lite.
						ProviderStructs.HtmlInfoStruct htmlInfo = GetHtmlText(Convert.ToInt32(item.StyleId));
						string content = htmlInfo.DesktopHtml;
						string title = htmlInfo.Title;

						// Next, we temporarily store the value in the database.
						MetaPostServices.SetTempStyleValue(userInfo.UserID, "styleId", item.StyleId);
						MetaPostServices.SetTempStyleValue(userInfo.UserID, "content", content);
						MetaPostServices.SetTempStyleValue(userInfo.UserID, "title", title);

						// Update the Expandable HTML/Text

						UpdateHtmlText(Convert.ToInt32(item.StyleId), item.Title, item.Content);

						// We set a TempInstallUrl since this is used by metaPost to redirect to the right
						// page when performing style detection.
						ModuleController moduleController = new ModuleController();
						ModuleInfo moduleInfo = moduleController.GetModule(htmlInfo.Id, Null.NullInteger, false);
						MetaPostServices.SetTempInstallUrl(ProviderKey, Globals.NavigateURL(moduleInfo.TabID));

						newId = item.StyleId;
					}
					catch
					{
						// No problem if we fail, the template just won't be available.
					}
				}
				else
				{
					throw new MetaPostException("FeatureNotImplemented",
					                            "This feature is currently not implemented.");
				}
			}
			else // Otherwise, it's a content region (page)
			{

				ExpandableTextHtmlController controller = new ExpandableTextHtmlController();
				ExpandableTextHtmlInfo contentItem = new ExpandableTextHtmlInfo();



				if (string.IsNullOrEmpty(item.ParentId) || item.ParentId == "0")
				{
					throw new MetaPostException("PageParentRequired", "A page parent is required.  In Windows Live Writer, the Page Parent dropdown appears in the bottom left part of the screen.");
				}

				int moduleId = Convert.ToInt32(item.ParentId);

				contentItem.Title = item.Title;
				contentItem.Body = item.Content;

				contentItem.LastUpdated = DateTime.Now;
				contentItem.ModuleId = moduleId;
				contentItem.ItemId = Null.NullInteger;
				contentItem.IsExpanded = true; // For now, we'll set it to false, but we may want to pass this 
				// through as a category property like with other providers.
				contentItem.SortOrder = Convert.ToInt32(item.PageOrder);

				controller.AddExpandableTextHtml(contentItem);

				// We need the itemId of the item added, so retrieve the regions and loop
				List<ExpandableTextHtmlInfo> regions =  controller.GetExpandableTextHtmls(moduleId, string.Empty);
				foreach(ExpandableTextHtmlInfo region in regions)
				{
					if (region.Title == item.Title)
					{
						newId = region.ItemId.ToString();
						break;
					}
				}

				// We set a TempInstallUrl since this is used by metaPost to redirect to the right
				// page when performing style detection.
				ModuleController moduleController = new ModuleController();
				ModuleInfo moduleInfo = moduleController.GetModule(moduleId, Null.NullInteger, false);
				MetaPostServices.SetTempInstallUrl(ProviderKey, Globals.NavigateURL(moduleInfo.TabID));

			}

			MetaPostServices.SaveItemImages(Convert.ToInt32(newId), images, ProviderKey, portalSettings, userInfo.UserID);

			return newId;
		}

		/// <summary>
		/// EditItem is called to edit either a page or a post.  The difference
		/// between the two is denoted in a property of the Item struct (ItemType).
		/// An additional property of the Item struct that may be important to the 
		/// implementation of the EditItem method is the Publish property.  This is 
		/// set to true when the item should be published.  The default setting of 
		/// false denotes that the item may be saved as a draft.
		/// </summary>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="item"></param>
		/// <returns></returns>
		public bool EditItem(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, Item item)
		{

			List<string> images = MetaPostServices.GetImageListFromItem(ref item);

			if (item.ItemType == ItemType.Post)
			{
				int moduleId = Convert.ToInt32(item.ItemId);
				UpdateHtmlText(moduleId, item.Title, item.Content);

				SetRedirect(moduleId);

			}
			else // Else if Page
			{
				ExpandableTextHtmlController controller = new ExpandableTextHtmlController();
				ExpandableTextHtmlInfo contentItem = new ExpandableTextHtmlInfo();

				int parentId = 0;
				int moduleId;
				if (item.ParentId != null
					&& int.TryParse(item.ParentId, out parentId))
				{

					if (parentId == 0)
					{
						throw new MetaPostException("PageParentRequired", "A page parent is required.  In Windows Live Writer, the Page Parent dropdown appears in the bottom left part of the screen.");
					}

					// User has selected a new parent
					moduleId = parentId;
				}
				else
				{
					// Otherwise, the same parent is selected, so we'll look up the moduleId from the ItemId
					moduleId = GetModuleIdFromItemId(item.ItemId.ToString());
				}

				contentItem.Title = item.Title;
				contentItem.Body = item.Content;

				contentItem.LastUpdated = DateTime.Now;
				contentItem.ModuleId = moduleId;
				contentItem.ItemId = Convert.ToInt32(item.ItemId);
				contentItem.IsExpanded = false; // For now, we'll set it to false, but we may want to pass this 
												// through as a category property like with other providers.
				contentItem.SortOrder = Convert.ToInt32(item.PageOrder);

				controller.UpdateExpandableTextHtml(contentItem);

				SetRedirect(moduleId);
			}

			MetaPostServices.SaveItemImages(Convert.ToInt32(item.ItemId), images, ProviderKey,
								portalSettings, userInfo.UserID);


			return true;
		}

		/// <summary>
		/// DeleteItem is used to delete either a page or a post.  The difference between the two is noted 
		/// in the itemType parameter which is an enumeration set to either Page or Post.
		/// </summary>
		/// <param name="itemId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="itemType"></param>
		/// <returns></returns>
		public bool DeleteItem(string itemId, UserInfo userInfo, PortalSettings portalSettings, ItemType itemType)
		{

			if (itemType == ItemType.Post)
			{
				// Check to see if a temporary style post was made and needs to be restored.
				string styleId = string.Empty;
				string styleText = string.Empty;
				string styleTitle = string.Empty;

				try
				{
					styleId = MetaPostServices.GetTempStyleValue(userInfo.UserID, "styleId");
					styleText = MetaPostServices.GetTempStyleValue(userInfo.UserID, "content");
					styleTitle = MetaPostServices.GetTempStyleValue(userInfo.UserID, "title");

					// Check to make sure the styleText isn't null or empty
					if (string.IsNullOrEmpty(styleText)) styleText = "";
				}
				catch
				{
				} // Ignore any failures.

				if (!string.IsNullOrEmpty(styleId))
				{
					try
					{
						// We will simply restore the text from the old version by retrieving it and 
						ProviderStructs.HtmlInfoStruct htmlInfo = GetHtmlText(Convert.ToInt32(styleId));
						UpdateHtmlText(Convert.ToInt32(styleId), styleTitle, styleText);
					}
					catch
					{
					} // Ignore failure.
				}
			}
			else // This is a content region (page) which needs to be deleted
			{
				ExpandableTextHtmlController controller = new ExpandableTextHtmlController();
				int moduleId = GetModuleIdFromItemId(itemId);
				int contentId = Convert.ToInt32(itemId);
				controller.DeleteExpandableTextHtml(moduleId, contentId);	
			}

			return true;
		}

		# endregion

		# region Category Related Procedures

		/// <summary>
		/// GetCategories retrieves a list of categories for a particular module.
		/// </summary>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>
		public ItemCategoryInfo[] GetCategories(string moduleLevelId, UserInfo userInfo,
												PortalSettings portalSettings)
		{
			ItemCategoryInfo[] categories = null;

			//throw new MetaPostException("FeatureNotImplemented", "This feature is currently not implemented.");

			return categories;
		}

		public bool SetItemCategories(string itemId, ItemCategory[] itemCategories, UserInfo userInfo,
		                        PortalSettings portalSettings)
		{
			// Not implemented in this provider.
			return true;
		}

		public ItemCategory[] GetItemCategories(string itemId, UserInfo userInfo,
		                                        PortalSettings portalSettings)
		{
			// Not implemented in this provider.
			return null;
		}

		/// <summary>
		/// Used to add a new category for modules which support this feature.
		/// </summary>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>
		public int NewCategory(string moduleLevelId, NewItemCategory category, UserInfo userInfo, PortalSettings portalSettings)
		{
			int categoryId = 0;
			// Check first to see if the user should have the ability to add Categories

			ModuleController moduleController = new ModuleController();
			//ModuleInfo moduleInfo =
			//    moduleController.getm(portalSettings.PortalId, moduleName);
			//bool bHasAdminRights = PortalSecurity.IsInRoles(_moduleConfiguration.AuthorizedEditRoles);
			//Dim blnHasModuleEditPermissions As Boolean = PortalSecurity.IsInRoles(_moduleConfiguration.AuthorizedEditRoles)
			throw new MetaPostException("FeatureNotImplemented",
										"This feature is currently not implemented.");

			return categoryId;
		}

		# endregion

		#endregion

		# region Private Procedures Specific to Text/HTML Module

		private ProviderStructs.HtmlInfoStruct GetHtmlText(int moduleId)
		{
			ProviderStructs.HtmlInfoStruct htmlInfo = new ProviderStructs.HtmlInfoStruct();
			object[] methodParams = { moduleId };
			
			// Retrieve the title using the module controller
			ModuleController moduleController = new ModuleController();
			ModuleInfo moduleInfo = moduleController.GetModule(moduleId, Null.NullInteger);
			Hashtable moduleSettings = moduleController.GetModuleSettings(moduleId);
			htmlInfo.Id = moduleId;
			htmlInfo.Title = moduleInfo.ModuleTitle;
			htmlInfo.DesktopHtml = moduleSettings["ICG_ETH_Header"] as string;
			htmlInfo.CreatedDate = DateTime.Now;
			
			return htmlInfo;
		}

		private int GetModuleIdFromItemId(string id)
		{
			// Note: this should be replaced with code from one of the module's controllers if it exists.
			// Couldn't find an overload to one of the get methods which allowed the retrieval of an
			// item without the ModuleId and ItemId. 

			string sSQL =
					"SELECT ModuleId FROM {databaseOwner}{objectQualifier}ICG_ETH_ExpandableTextHtmlc WHERE ItemId = " + id.Replace("'", "''");
			int moduleId = -1;
            using (IDataReader dr = DataProvider.Instance().ExecuteSQL(sSQL))
            {

                while (dr.Read())
                {
                    moduleId = (int)dr["ModuleId"];
                }
            }

			return moduleId;
		}

		private void SetRedirect(int moduleId)
		{
			// Set the redirect URL so the page is shown in the browser after the update completes.
			ModuleController moduleController = new ModuleController();
			ModuleInfo moduleInfo = moduleController.GetModule(moduleId, Null.NullInteger, false);
			MetaPostServices.SetTempInstallUrl(ProviderKey, Globals.NavigateURL(moduleInfo.TabID));

		}

		private void UpdateHtmlText(int moduleId, string title, string desktopHtml)
		{
			// Also need to update the Module Title in case it changed.
			// Next, retrieve the title using the module controller
			ModuleController moduleController = new ModuleController();
			ModuleInfo moduleInfo = moduleController.GetModule(moduleId, Null.NullInteger);

			// Update the text
			moduleController.UpdateModuleSetting(moduleId, "ICG_ETH_Header", desktopHtml);

			// Update the title if a change was made
			moduleInfo.ModuleTitle = title;
			moduleController.UpdateModule(moduleInfo);
		}

		# endregion

		# region Private Comparer Classes

		private class ItemComparer : IComparer<Item>
		{
			public int Compare(Item x, Item y)
			{
				return x.Title.CompareTo(y.Title);
			}
		}

		# endregion


	}
}